# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012  Jean-Philippe Lang
# Copyright (C) 2007  Patrick Aljord patcito@ŋmail.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

require 'redmine/scm/adapters/plastic_adapter'

class Repository::Plastic < Repository

  safe_attributes 'selected_branch'
  
  validates_presence_of :url


  def self.human_attribute_name(attribute_key_name, *args)
    attr_name = attribute_key_name.to_s
    if attr_name == "url"
      attr_name = "plastic_repository_server"
    elsif attr_name == "root_url"
      attr_name = "plastic_path_to_workspace"
    end
    super(attr_name, *args)
  end
  
  def self.scm_adapter_class
    Redmine::Scm::Adapters::PlasticAdapter
  end

  def self.scm_name
    'PlasticSCM'
  end

  def extra_selected_branch
    default_selected_branch="/main"
    return default_selected_branch if extra_info.nil?
    v = extra_info["extra_selected_branch"]
    return default_selected_branch if v.nil?
    v.to_s
  end

  def extra_additional_branches
    default_additional_branches=''
    return default_additional_branches if extra_info.nil?
    v = extra_info["extra_additional_branches"]
    return default_additional_branches if v.nil?
    v.to_s
  end
  
  def extra_use_additional_branches
    default_use_additional_branches=true
    return default_use_additional_branches if extra_info.nil?
    v = extra_info["extra_use_additional_branches"]
    return default_use_additional_branches if v.nil?
    v.to_s != '0'
  end
  

  # return scm branches
  def branches(addtional_bracnhes=nil, use_additional_branches=true)
    scm.branches(addtional_bracnhes, use_additional_branches)
  end

  def default_branch
    scm.default_branch(extra_selected_branch)
  rescue Exception => e
    logger.error "plastic: error during get default branch: #{e.message}"
    nil
  end

  def tags
    scm.tags
  end
  
  def supports_directory_revisions?
    true
  end

  def fetch_changesets
    logger.debug {"<plastic> fetch_changesets start ----"}
    scm_brs = branches(extra_additional_branches, extra_use_additional_branches)
    return if scm_brs.nil? || scm_brs.empty?
    #make brs_hash
    scm_brs_hash = {}
    scm_brs.map do |br| 
      scm_brs_hash[br] = {}
      scm_brs_hash[br][:last_changeset] = br.revision
      scm_brs_hash[br][:is_selected] = true if br.is_selected
    end
    logger.debug {"<plastic> fetch_changesets scm_brs_hash=>#{scm_brs_hash}"}
    h1 = extra_info || {}
    h  = h1.dup
    #r epo_heads = scm_brs.map{ |br| br.revision }
    repo_heads = scm_brs_hash
    h["selected_branches"] ||= {}
    prev_db_heads = h["selected_branches"].dup
    if prev_db_heads.empty?
      prev_db_heads = heads_from_branches_hash
    end
    return if prev_db_heads.sort == repo_heads.sort

    h["db_consistent"] ||= {}
    if changesets.count == 0
      h["db_consistent"]["ordering"] = 1
      merge_extra_info(h)
      self.save
    elsif ! h["db_consistent"].has_key?("ordering")
      h["db_consistent"]["ordering"] = 0
      merge_extra_info(h)
      self.save
    end
    logger.debug { "<plasitc> fetch_changeset... h: #{h}"}
    logger.debug { "<plasitc> fetch_changeset... prev_db_heads: #{prev_db_heads}"}
    logger.debug { "<plasitc> fetch_changeset... repo_heads: #{repo_heads}"}
    save_revisions(prev_db_heads, repo_heads)    
  end

  # used by app/controllers/repositories_controller.rb:116:in `show'
  #
  def entries(path=nil, identifier=nil)
    #logger.debug { "<plastic> entries caller: #{p caller} "}
    entries = scm.entries(path, identifier)
    load_entries_changesets(entries)
    entries
  end

  def heads_from_branches_hash    
    logger.debug {"<plastic> heads_from_branches_hash"}
    h1 = extra_info || {}
    h  = h1.dup
    h["selected_branches"] ||= {}
     return h["selected_branches"]
  end
  
  private

  def save_revisions(prev_db_heads, repo_heads)
    logger.debug { "plasitc save_revisions: start"}
    h = {}
    opts = {}
    opts[:excludes] = prev_db_heads
    opts[:includes] = repo_heads

    revisions = scm.revisions('', nil, nil, opts)
    return if revisions.blank?  

    # Make the search for existing revisions in the database in a more sufficient manner
    #

    limit = 100
    offset = 0
    revisions_copy = revisions.clone # revisions will change
    while offset < revisions_copy.size
      recent_changesets_slice = changesets.find(
                                     :all,
                                     :conditions => [
                                        'scmid IN (?)',
                                        revisions_copy.slice(offset, limit).map{|x| x.scmid}
                                      ]
                                    )
      # Subtract revisions that redmine already knows about
      recent_revisions = recent_changesets_slice.map{|c| c.scmid}
      revisions.reject!{|r| recent_revisions.include?(r.scmid)}
      offset += limit
    end

    revisions.each do |rev|
      transaction do
        # There is no search in the db for this revision, because above we ensured,
        # that it's not in the db.
        save_revision(rev)
      end
    end
    h["selected_branches"] = repo_heads.dup
    merge_extra_info(h)
    self.save    
  end

  def save_revision(rev)
    parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
    changeset = Changeset.create(
              :repository   => self,
              :revision     => rev.identifier,
              :scmid        => rev.scmid,
              :committer    => rev.author,
              :committed_on => rev.time,
              :comments     => rev.message,
              :parents      => parents
              )
    unless changeset.new_record?
      rev.paths.each { |change| changeset.create_change(change) }
    end
    changeset
  end

end
